package com.cim120.commons.cip.utils;

import com.cim120.commons.cip.model.ImageInfo;
import org.apache.commons.lang3.StringUtils;
import org.im4java.core.ConvertCmd;
import org.im4java.core.IMOperation;
import org.im4java.core.IdentifyCmd;
import org.im4java.process.ArrayListOutputConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.*;


public class GraphicsMagickUtils {
    private static final Logger log = LoggerFactory.getLogger(GraphicsMagickUtils.class);

    /**
     * 图片质量
     */
    public static final String IMAGE_QUALITY = "Quality";
    /**
     * 图片高度
     */
    public static final String IMAGE_HEIGHT = "Height";
    /**
     * 图片宽度
     */
    public static final String IMAGE_WIDTH = "Width";
    /**
     * 图片格式
     */
    public static final String IMAGE_FORMAT = "Format";
    /**
     * 图片方向
     */
    public static final String IMAGE_ORIENTATION = "Orientation";
    // /////////////////////////////////////////////////////////////////////
    //
    // 图片质量处理
    //
    // /////////////////////////////////////////////////////////////////////

    /**
     * /**
     * 按照图片的原始尺寸进行图片缩放处理，并加上图片方向适配
     *
     * @param srcPath
     * @param desPath
     */
    public static boolean qualityByOrientaion(String srcPath, String desPath, int quality) {
        return qualityByOrientaion(srcPath, desPath, quality, null, null);
    }

    public static boolean qualityByOrientaion(String srcPath, String desPath, int quality, double percentage) {
        return qualityByOrientaion(srcPath, desPath, quality, percentage, null);
    }

    public static boolean qualityByOrientaion(String srcPath, String desPath, int quality, Double percentage, ImageInfo info) {
        if (StringUtils.isNotEmpty(srcPath) && StringUtils.isNotEmpty(desPath)) {
            if (info == null) {
                info = getImageInfo(srcPath);
            }
            if (info != null) {
                try {
                    IMOperation op = new IMOperation();
                    op.addImage(srcPath);
//                    op.resize(info.getWidth(), info.getHeight());
                    op = getRotateOpeation(op, info.getOrientation());
//                    op.sample(info.getWidth(), info.getHeight());
                    op = getThumbnailOpeation(op, info, percentage);
                    op.addRawArgs("-quality", String.valueOf(quality));
                    op.addRawArgs("+profile", "*");
                    op.strip();
                    op.addImage(desPath);
                    ConvertCmd cmd = new ConvertCmd(true);
                    cmd.run(op);
                    return true;
                } catch (Exception e) {
                    if (log.isErrorEnabled())
                        log.error("resize image by width and height error:'" + e.getMessage() + "'");
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    // /////////////////////////////////////////////////////////////////////
    //
    // 图片缩放处理
    //
    // /////////////////////////////////////////////////////////////////////

    /**
     * 按照宽度来缩放图片，注意这是有条件缩放，如果原图宽度不满足width指定的大小，会保持
     * 原图的宽度大小。
     *
     * @param srcPath
     * @param desPath
     * @param width
     * @return resize status
     * @author tonydeng
     */
    public static boolean resizeByWidth(String srcPath, String desPath, int width) {
        try {
            IMOperation op = new IMOperation();
            op.addImage(srcPath);
            op.resize(width, null);
            op.addImage(desPath);
            ConvertCmd cmd = new ConvertCmd(true);
            cmd.run(op);

            return true;
        } catch (Exception e) {
            if (log.isErrorEnabled())
                log.error("resizeImageByWidth src='" + srcPath + "' des='" + desPath + "'  error: '" + e.getMessage() + "'");
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 固定宽度缩放，以参数指定的width为准，如果原图不满足这个宽度，会“扩”到width大小，不以原图的宽度为准绳。
     *
     * @param srcPath
     * @param desPath
     * @param width
     * @return
     */
    public static boolean resizeByWidthWithRAW(String srcPath, String desPath, int width) {
        try {
            IMOperation op = new IMOperation();
            op.addImage(srcPath);
            op.addRawArgs("-resize", width + "");
            op.resize(width);
            op.addImage(desPath);
            ConvertCmd cmd = new ConvertCmd(true);
            cmd.run(op);

            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 按照指定的宽度和高度来缩放图片；宽高可以选择其一为空；如果都为空，则不处理。
     *
     * @param srcPath 不能为空
     * @param desPath 不能为空
     * @param width
     * @param height
     * @return resize status
     */
    public static boolean resizeByWidthAndHeight(String srcPath, String desPath, Integer width, Integer height) {
        if (srcPath != null && desPath != null && (width != null || height != null)) {
            try {
                IMOperation op = new IMOperation();
                op.addImage(srcPath);
                op.resize(width, height);
//                op.colorspace(ImageEnum.Color.RED.name());
                op.strip();
                op.addImage(desPath);
                ConvertCmd cmd = new ConvertCmd(true);
                cmd.run(op);
                return true;
            } catch (Exception e) {
                if (log.isErrorEnabled())
                    log.error("resize image by width and height error:'" + e.getMessage() + "'");
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 固定宽高设定的缩放，不按照等比缩放的原则，按照设定的宽和高进行固定缩放。
     *
     * @param srcPath
     * @param desPath
     * @param width
     * @param height
     * @return
     * @author huangpc
     */
    public static boolean resizeByfixedWidthAndHeight(String srcPath, String desPath, Integer width, Integer height) {
        if (srcPath != null && desPath != null && (width != null || height != null)) {
            try {
                IMOperation op = new IMOperation();
                op.addImage(srcPath);
                op.addRawArgs("-resize", width + "x" + height + "!");
//                op.colorspace(ImageEnum.Color.RED.name());
                op.strip();
                op.addImage(desPath);
                ConvertCmd cmd = new ConvertCmd(true);
                cmd.run(op);
                return true;
            } catch (Exception e) {
                if (log.isErrorEnabled())
                    log.error("resize image by width and height error:'" + e.getMessage() + "'");
                e.printStackTrace();
            }
        }
        return false;
    }

    // /////////////////////////////////////////////////////////////////////
    //
    // 图片截图
    //
    // /////////////////////////////////////////////////////////////////////

    /**
     * 先缩放，然后居中切割图片
     *
     * @param srcPath 原图片
     * @param desPath 目标图片
     * @param rectw   待切割的宽度
     * @param recth   待切割的高度
     * @author tonydeng
     */
    public static boolean cutCenter(String srcPath, String desPath, int rectw, int recth) {

        try {
            IMOperation op = new IMOperation();
            op.addImage();
            op.thumbnail(rectw, recth, "^").gravity("center").extent(rectw, recth);
            op.addImage();

            ConvertCmd convert = new ConvertCmd(true);
            convert.run(op, srcPath, desPath);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 针对GIF图的第一帧，先缩放，然后居中切割图片
     *
     * @param srcPath 原图片
     * @param desPath 目标图片
     * @param rectw   待切割的宽度
     * @param recth   待切割的高度
     * @author tonydeng
     */
    public static boolean cutGifCenter(String srcPath, String desPath, int rectw, int recth) {
        try {
            IMOperation op = new IMOperation();
            op.addImage();
            op.thumbnail(rectw, recth, "^").gravity("center").extent(rectw, recth);
            op.addImage();
            ConvertCmd convert = new ConvertCmd(true);
            convert.run(op, srcPath + "[0]", desPath);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 先缩放，然后以左上角为基点切割图片
     *
     * @param srcPath 原图片
     * @param desPath 目标图片
     * @param rectw   待切割的宽度
     * @param recth   待切割的高度
     * @author tonydeng
     */
    public static boolean cutTop(String srcPath, String desPath, int rectw, int recth) {

        try {
            IMOperation op = new IMOperation();
            op.addImage();
            op.resize(rectw, recth, "^").gravity("NorthWest").extent(rectw, recth);
            op.addImage();
            ConvertCmd convert = new ConvertCmd(true);
            convert.run(op, srcPath, desPath);
            return true;
        } catch (Exception e) {

            if (log.isErrorEnabled())
                log.error("crop image for top error:' IM4JavaException " + e.getMessage() + "'");
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 针对GIF图的第一帧，先缩放，然后左上角为基点切割图片
     *
     * @param srcPath 原图片
     * @param desPath 目标图片
     * @param rectw   待切割的宽度
     * @param recth   待切割的高度
     * @author tonydeng
     */
    public static boolean cutGifTop(String srcPath, String desPath, int rectw, int recth) {

        try {
            IMOperation op = new IMOperation();
            op.addImage();
            op.resize(rectw, recth, "^").gravity("NorthWest").extent(rectw, recth);
            op.addImage();

            ConvertCmd convert = new ConvertCmd(true);
            convert.run(op, srcPath + "[0]", desPath);
            return true;
        } catch (Exception e) {
            if (log.isErrorEnabled())
                log.error("crop image for top error:' IM4JavaException " + e.getMessage() + "'");
            e.printStackTrace();
        }
        return false;
    }

    /**
     * @param srcPath
     * @param desPath
     * @param rectw
     * @param recth
     * @param x
     * @param y
     * @param isResize
     * @param isCrop
     * @author tonydeng
     * 裁切指定坐标和宽高部分的图片
     */
    public static boolean cutByCoord(String srcPath, String desPath, int rectw, int recth, int x, int y, boolean isResize, boolean isCrop) {
        try {
            IMOperation op = new IMOperation();
            op.addImage();

            if (isResize)
                op.resize(rectw, null);
            if (isCrop)
                op.crop(rectw, recth, x, y);
            op.addImage();
            ConvertCmd convert = new ConvertCmd(true);
            convert.run(op, srcPath + "[0]", desPath);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 图片裁剪,等比压缩
     *
     * @param srcPath 源图片
     * @param desPath 处理后图片
     * @param sw      源图片宽
     * @param sh      源图片高
     * @param dw      处理后图片宽
     * @param dh      处理后图片高
     * @return 0:正常 ，1：裁剪失败 ，-1：输入的参数有误
     */
    public static int cut(String srcPath, String desPath, int sw, int sh,
                          int dw, int dh) {
        if (sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0) {
            return -1;
        }
        try {
            IMOperation op = new IMOperation();
            op.addImage();

            // 如果源图宽度和高度都小于目标宽高，则仅仅压缩图片
            if ((sw <= dw) && (sh <= dh)) {
                op.resize(sw, sh);
            }

            // 如果源图宽度小于目标宽度，并且源图高度大于目标高度
            if ((sw <= dw) && (sh > dh)) {
                op.resize(sw, sh); // 压缩图片
                op.append().crop(sw, dh, 0, (sh - dh) / 2); // 切割图片
            }

            // 如果源宽度大于目标宽度，并且源高度小于目标高度
            if ((sw > dw) && (sh <= dh)) {
                op.resize(sw, sh);
                op.append().crop(dw, sh, (sw - dw) / 2, 0);
            }

            // 如果源图宽、高都大于目标宽高
            if (sw > dw && sh > dh) {
                float ratiow = (float) dw / sw; // 宽度压缩比
                float ratioh = (float) dh / sh; // 高度压缩比

                // 宽度压缩比小（等）于高度压缩比（是宽小于高的图片）
                if (ratiow >= ratioh) {
                    int ch = (int) (ratiow * sh); // 压缩后的图片高度
                    op.resize(dw, ch); // 按目标宽度压缩图片
                    op.append()
                            .crop(dw, dh, 0, (ch > dh) ? ((ch - dh) / 2) : 0); // 根据高度居中切割压缩后的图片
                } else { // （宽大于高的图片）
                    int cw = (int) (ratioh * sw); // 压缩后的图片宽度
                    op.resize(cw, dh); // 按计算的宽度进行压缩
                    op.append()
                            .crop(dw, dh, (cw > dw) ? ((cw - dw) / 2) : 0, 0); // 根据宽度居中切割压缩后的图片
                }
            }

            op.addImage();
            ConvertCmd convert = new ConvertCmd(true);
            convert.run(op, srcPath, desPath);// BufferedImage or String

        } catch (Exception e) {
            log.error("crop image error:'", e, "'");
            return 1;
        }
        return 0;
    }
    // /////////////////////////////////////////////////////////////////////
    //
    // 图片信息获取
    //
    // /////////////////////////////////////////////////////////////////////

    /**
     * 获得图片信息（宽高、图片格式、方向）
     *
     * @param image
     * @return
     */
    public static ImageInfo getImageInfo(String image) {
        ImageInfo info = new ImageInfo();
        Map<String, String> basicInfo = getImageBasicInfo(image);
        info.setHeight(parseInt(basicInfo.get(IMAGE_HEIGHT)));
        info.setWidth(parseInt(basicInfo.get(IMAGE_WIDTH)));
        info.setFormat(basicInfo.get(IMAGE_FORMAT));
        info.setOrientation(parseInt(basicInfo.get(IMAGE_ORIENTATION)));
        return info;
    }

    /**
     * 查询图片的基本信息:格式,质量，宽度，高度
     * <p>
     * <pre>
     *    %b   file size of image read in
     *    %c   comment property
     *    %d   directory component of path
     *    %e   filename extension or suffix
     *    %f   filename (including suffix)
     *    %g   layer canvas page geometry   ( = %Wx%H%X%Y )
     *    %h   current image height in pixels
     *    %i   image filename (note: becomes output filename for "info:")
     *    %k   number of unique colors
     *    %l   label property
     *    %m   image file format (file magic)
     *    %n   exact number of images in current image sequence
     *    %o   output filename  (used for delegates)
     *    %p   index of image in current image list
     *    %q   quantum depth (compile-time constant)
     *    %r   image class and colorspace
     *    %s   scene number (from input unless re-assigned)
     *    %t   filename without directory or extension (suffix)
     *    %u   unique temporary filename (used for delegates)
     *    %w   current width in pixels
     *    %x   x resolution (density)
     *    %y   y resolution (density)
     *    %z   image depth (as read in unless modified, image save depth)
     *    %A   image transparency channel enabled (true/false)
     *    %C   image compression type
     *    %D   image dispose method
     *    %G   image size ( = %wx%h )
     *    %H   page (canvas) height
     *    %M   Magick filename (original file exactly as given,  including read mods)
     *    %O   page (canvas) offset ( = %X%Y )
     *    %P   page (canvas) size ( = %Wx%H )
     *    %Q   image compression quality ( 0 = default )
     *    %S   ?? scenes ??
     *    %T   image time delay
     *    %W   page (canvas) width
     *    %X   page (canvas) x offset (including sign)
     *    %Y   page (canvas) y offset (including sign)
     *    %Z   unique filename (used for delegates)
     *    %@   bounding box
     *    %#   signature
     *    %%   a percent sign
     *    \n   newline
     *    \r   carriage return
     * </pre>
     *
     * @param image 图片完整路径
     * @return 返回图片的属性Map信息, 如果出错，则返回空Map对象
     */
    public static Map<String, String> getImageBasicInfo(String image) {
        if (StringUtils.isNotEmpty(image) && new File(image).exists()) {
            // create operation
            IMOperation op = new IMOperation();
            op.ping();
            op.addImage(image);
            op.format("%m\n%w\n%h\n%q\n%[exif:Orientation]");
            try {

                // execute ...
                IdentifyCmd cmd = new IdentifyCmd(true);
                ArrayListOutputConsumer output = new ArrayListOutputConsumer();
                cmd.setOutputConsumer(output);
                cmd.run(op);

                // ... and parse result
                ArrayList<String> cmdOutput = output.getOutput();
                Iterator<String> iter = cmdOutput.iterator();
                Map<String, String> map = new Hashtable<String, String>();
                map.put(IMAGE_FORMAT, iter.next());
                map.put(IMAGE_WIDTH, iter.next());
                map.put(IMAGE_HEIGHT, iter.next());
                map.put(IMAGE_QUALITY, iter.next());
                map.put(IMAGE_ORIENTATION, iter.next());
                return map;
            } catch (Exception e) {
                if (log.isErrorEnabled())
                    log.error("identify image basic  info {} failed. error:'{}''", image, e);
            }
        }
        return Collections.emptyMap();
    }
    // /////////////////////////////////////////////////////////////////////
    //
    // 图片处理参数自动适配
    //
    // /////////////////////////////////////////////////////////////////////

    /**
     * 根据压缩百分百，生成图片缩略图操作选项
     *
     * @param operation
     * @param info
     * @param percentage
     * @return
     */
    public static IMOperation getThumbnailOpeation(IMOperation operation, ImageInfo info, Double percentage) {
        if (operation != null && info != null) {
            if (percentage != null && percentage > 0 && percentage < 1) {
                operation.thumbnail(new Double(info.getWidth() * percentage).intValue(),
                        new Double(info.getHeight() * percentage).intValue());
            } else {
                operation.sample(info.getWidth(), info.getHeight());
            }
        }
        return operation;
    }

    /**
     * 根据获取的图片方向，生成图片操作选项
     *
     * @param operation
     * @param orientation
     * @return
     */
    public static IMOperation getRotateOpeation(IMOperation operation, Integer orientation) {
        if (operation != null && orientation != null) {
            switch (orientation) {
                case 1:
                    break;
                case 2:
                    operation.flop();
                    break;
                case 3:
                    operation.rotate(180.0);
                    break;
                case 4:
                    operation.flip();
                    break;
                case 5:
                    operation.transpose();
                    break;
                case 6:
                    operation.rotate(90.0);
                    break;
                case 7:
                    operation.transverse();
                    break;
                case 8:
                    operation.radialBlur(270.0);
                    break;
                default:
                    break;
            }
        }
        return operation;
    }

    private static int parseInt(String value) {
        try {
            return Integer.parseInt(value);
        } catch (Exception e) {
            return -1;
        }
    }
}
